¿Hay alguna diferencia entre los siguientes dos fragmentos de código?
class Test { public readonly double Val; public Test(bool src) { this.Val = src ? 1 : 0; } } class Test { public readonly double Val; public Test(bool src) { this.Val = src ? 1D : 0D; } }Descubrí que nuestro código base utiliza la segunda forma de escritura.
Aquí hay dos preguntas y es importante señalar que tienen respuestas diferentes.
¿Hay alguna diferencia entre
double val = 1;ydouble val = 1D;?
No. El compilador de C# reconoce cuándo se usa un literal entero en un contexto en el que se espera un doble y cambia el tipo en el momento de la compilación, por lo que estos dos fragmentos generarán el mismo código.
¿Hay alguna diferencia entre los siguientes dos fragmentos de código?
double Val; ... this.Val = src ? 1 : 0; --- this.Val = src ? 1D : 0D; Si. La regla de que las constantes enteras se cambian automáticamente a dobles solo se aplica a las constantes y src ? ... no es una constante . El compilador generará el primero como si hubieras escrito:
int t; if (src) t = 1; else t = 0; this.Val = (double)t;Y el segundo como
double t; if (src) t = 1D; else t = 0D; this.Val = t;Es decir, en la primera elegimos un número entero y luego lo convertimos a doble, y en la segunda elegimos un doble.
FYI: el compilador de C# o el jitter pueden reconocer que el primer programa se puede optimizar en el segundo, pero no sé si realmente lo hace. El compilador de C# a veces mueve las conversiones de la aritmética elevada al cuerpo de los condicionales; Escribí ese código hace unos ocho años, pero no recuerdo todos los detalles.
Hay una diferencia en el código IL generado.
Esta clase:
class Test1 { public readonly double Val; public Test1(bool src) { this.Val = src ? 1 : 0; } }Produce este código IL para el constructor:
.class private auto ansi beforefieldinit Demo.Test1 extends [mscorlib]System.Object { .field public initonly float64 Val .method public hidebysig specialname rtspecialname instance void .ctor ( bool src ) cil managed { IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ldarg.0 IL_0007: ldarg.1 IL_0008: brtrue.s IL_000d IL_000a: ldc.i4.0 IL_000b: br.s IL_000e IL_000d: ldc.i4.1 IL_000e: conv.r8 IL_000f: stfld float64 Demo.Test1::Val IL_0014: ret } }Y esta clase:
class Test2 { public readonly double Val; public Test2(bool src) { this.Val = src ? 1d : 0d; } }Produce este código IL para el constructor:
.class private auto ansi beforefieldinit Demo.Test2 extends [mscorlib]System.Object { .field public initonly float64 Val .method public hidebysig specialname rtspecialname instance void .ctor ( bool src ) cil managed { IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ldarg.0 IL_0007: ldarg.1 IL_0008: brtrue.s IL_0015 IL_000a: ldc.r8 0.0 IL_0013: br.s IL_001e IL_0015: ldc.r8 1 IL_001e: stfld float64 Demo.Test2::Val IL_0023: ret } } Como puede ver, en la primera versión tiene que llamar a conv.r8 para convertir un int en un doble.
Sin embargo: (1) el resultado final es idéntico y (2) el compilador JIT bien puede traducir ambos al mismo código de máquina.
Entonces, la respuesta es: sí, hay una diferencia, pero no una de la que deba preocuparse.
Personalmente, optaría por la segunda versión, ya que expresa mejor la intención del programador y puede producir un código ligeramente más eficiente (dependiendo de lo que haga el compilador JIT).
No hay diferencia, el compilador es lo suficientemente inteligente como para hacer una conversión implícitamente o no.
Sin embargo, si usa var , debe escribir var val = 42D; para asegurarse de que la variable sea un doble y no un int.
double foo = 1; // This is a double having the value 1 double bar = 1d; // This is a double having the value 1 var val = 42d; // This is a double having the value 42 var val2 = 42; // /!\ This is an int having the value 42 !! /!\